#include "coder.h"
#include "log.h"
#include "rawdata.h"
#include "compresseddata.h"
#include <sstream>
#include <cmath>

using namespace std;

void Coder::encode(RawDataInput& input, CompressedDataOutput& output, int M)
{
	ostringstream ss;
	ss << "Encoding (M=" << M << ")...";
	logger.info(ss.str(), true);

	double log2M = log(M) / log(2);
	int cutoff = (int) pow(2, ceil(log2M)) - M;
	int c = 0;

	for (int i = 0; i < input.length(); i++)
	{
		int symbol = input[i];
		debugSymbols(symbol);
		int q = symbol / M;
		for (int j = 0; j < q; j++)
		{
			output.put(1);
			debugCodes(1);
		}
		output.put(0);
		debugCodes(0);

		int r = symbol % M;
		int bits = ceil(log2M) - 1;
		if (r >= cutoff)
		{
			r += cutoff;
			bits++;
		}
		while (bits > 0)
		{
			int b = (r >> --bits) & 1;
			output.put(b);
			debugCodes(b);
		}
		debugCodes(0, D_END);

		logger.progress(++c, input.length());
	}
	logger.debug("symbol sequence:", *debugSymbols(0, D_FIN));
	logger.debug("code sequence:", *debugCodes(0, D_FIN));
}

void Coder::decode(CompressedDataInput& input, RawDataOutput& output, int M)
{
	ostringstream ss;
	ss << "Decoding (M=" << M << ")...";
	logger.info(ss.str(), true);

	double log2M = log(M) / log(2);
	int cutoff = (int) pow(2, ceil(log2M)) - M;

	while (!input.END())
	{
		int q = 0;
		int r = 0;

		while (input.get())
		{
			debugCodes(1);
			q++;
		}
		debugCodes(0);
		int symbol = q * M;

		for (int j = 0; j < (int) ceil(log2M) - 1; j++)
		{
			r <<= 1;
			int b = input.get();
			r |= b;
			debugCodes(b);
		}

		if (r >= cutoff && M > 1)
		{
			r <<= 1;
			int b = input.get();
			r |= b;
			r -= cutoff;
			debugCodes(b);
		}

		symbol += r;
		output.put(symbol);
		debugSymbols(symbol);

		logger.progress(input.pos() + 1, input.length());
		debugCodes(0, D_END);
	}
	logger.debug("code sequence:", *debugCodes(0, D_FIN));
	logger.debug("symbol sequence:", *debugSymbols(0, D_FIN));
}

int Coder::adapt(std::vector<RawDataInput::Comparator> dictBuilder)
{
	int N = 0;
	double S = 0;
	double Sigma = 0;
	for (; N < dictBuilder.size(); N++)
	{
		S += dictBuilder[N].count;
		if (dictBuilder[N].count == 0)
			break;
	}
	for (int i = 0; i < N; i++)
	{
		Sigma += (double) i * dictBuilder[i].count;
	}
	Sigma /= (N * S);
	Sigma += 1;
	double p = 1.0 / Sigma;

	double M = log(1.0 + p) / log(1.0 / p);
	return (int) ceil(M);
}

string* Coder::debugCodes(int bit, Mode mode)
{
	static ostringstream bits;
	static int padding = 0;
	static string empty = "";
	static string debugStr;

	if (!debug)
	{
		return &empty;
	}
	if (mode == D_END)
	{
		bits.seekp(-1, ios_base::cur);
		bits << '|';
		return &empty;
	}
	if (mode == D_FIN)
	{
		bits << '\n';
		debugStr = string(bits.str());
		padding = 0;
		bits.str("");
		return &debugStr;
	}
	if (padding == 8)
	{
		bits << "  ";
	}
	if (padding == 16)
	{
		bits << "\n";
		padding = 0;
	}
	bits << bit << ' ';
	padding++;
	return &empty;
}

string* Coder::debugSymbols(int symbol, Mode mode)
{
	static ostringstream symbols;
	static int padding = 0;
	static string empty = "";
	static string debugStr;

	if (!debug)
	{
		return &empty;
	}
	if (mode == D_FIN)
	{
		symbols << '\n';
		debugStr = string(symbols.str());
		padding = 0;
		symbols.str("");
		return &debugStr;
	}
	if (padding == 16)
	{
		symbols << "\n";
		padding = 0;
	}
	symbols << symbol << ' ';
	padding++;
	return &empty;
}

